
/**
 * @typedef DataProviderCallbacks
 * @type {object}
 * @property {function} onInitChart -onInitChart
 * @property {function} onChartConnectionOpened -onChartConnectionOpened
 * 
 */

 /**
 * @typedef MoreDataRequestOptions
 * @type {object}
 * @property {number} startTime - startTime timestamp like 1552579204000, required
 * @property {number} endTime - endTime timestamp like 1552579204000,required
 * 
 * 
 */

 /**
 * @typedef DataTypeChangeOptions
 * @type {object}
 * @property {number} sampleInterval -if <= 0, means it's tickdata
 * @property {number} [maxDataPoint] -this is only for tickData
 * 
 */


  /**
   * configSource: "Excel"
 * @typedef ConfigRequestOptions
 * @type {object}
 * @property {string} chartName - chartName ,key, required
 * @property {string} chartType - chartType : line, OHLC
 * @property {string} configSource - configSource : Excel ,PTW, Mobile
 * @property {chartTitle} [chartTitle] - endTime timestamp like 1552579204000,required
 * @property {DataOptions} dataOptions - dataOptions
 * @property {QueryConfig[]} [queryConfigs] - queryConfigs
 * @property {RangeConfig[]} [rangeConfigs] - rangeConfigs 
 * @property {DirectionConfig[]} [directionConfigs] - directionConfigs
 * 
 */


 /**
  * @typedef StartCallback
  * @type {function}
  * @param {string} rquestId - rquestId
  * @param {object} payload - payload
  * 
  */

/**
  * @typedef EndCallback
  * @type {Function}
  * @param {string} rquestId - rquestId
  * @param {object} payload - payload
  * 
  */

  /**
  * @typedef QueryResponseCallback
  * @type {function}
  * @param {string} rquestId - rquestId
  * @param {object} payload - payload
  * 
  */

  /**
  * @typedef SubscribeResponseCallback
  * @type {function}
  * @param {string} rquestId - rquestId
  * @param {object} payload - payload
  * 
  */

  /**
   * ResponseErrorCallback
  * @typedef ResponseErrorCallback
  * @type {function}
  * @param {string} rquestId - rquestId
  * @param {object} payload - payload
  * 
  */
 


 /**
  * @typedef DataCallbacks
  * @type {object}
  * @property {StartCallback} [onStart] - start marker for request
  * @property {EndCallback} [onEnd] - end marker for request
  * @property {QueryResponseCallback} [onResponse] - history data  for request
  * @property {SubscribeResponseCallback} [onEvent] - realtime  data for request
  * @property {ResponseErrorCallback} [onError] - error data for request
  */



/**
 * ChartProvider
 * @class
 * @description  --for server side data, we have tick data and sample data, if is sample data,should also tell client time interval1. server should tell client data is sample or not,please always give x,open,high,low,close2. for sample data, each time interval, server side should publish data at the same time.
 * 
 * @property {string} chartDataType -tick,sampled, default is sampled
 * @property {RxSubject} subject -rx subject, data stream
 * @property {object} chartConfig -provider need know bussiness hours ,  we need drop data that not in bussiness hours. how about user update bussiness hours?
 * @property {string} appId - uuid for every chartConnection
 * @property {ChartConnection} chartConnection -connection to cep
 * @property {ChartConnection} [excelConnection] - connection to excel
 */
function ChartDataProvider() {

    this.excelConnection = null;
    this.chartConnection = null;
    this.redrawInterval = 1000;
    this.historicalStartTime = null;
    this.logger = console;
  
    this.chartConfig = null;
   
    this.subject = new Rx.Subject();
  
    this.chartDataType = "sampled";

    this.appId = null;
}

/**
 * @description send msg out to server or excel
 * @param {json} msg - json object msg
 * @param {ChartConnection} connection - connection to excel or server
 */
ChartDataProvider.prototype.sendMsg = function (msg,connection) {
    if(connection && connection.connectionStatus){
        connection.sendMsg(msg);
    }else{
        if(this.chartConnection && this.chartConnection.connectionStatus){
            this.chartConnection.sendMsg(msg);
        }else{
            if(this.excelConnection && this.excelConnection.connectionStatus){
                this.excelConnection.sendMsg(msg);
            }else{
                self.logger.warn("no connection,drop msg",msg);
            }
        }
    }
}

/**
 * @private
 */
ChartDataProvider.prototype._init = function () {
   
    // PTW \ Mobile \ Excel
    let configSource = getParameterByName("configSource");
    if (configSource == "Excel") {
        // connect to excelConnection
        this._initExcelConnection();
    } else if (configSource == "PTW") {

    } else if (configSource == "Mobile") {

    } else if (configSource == "Local") {

    }

}



/**
 * @description  setup data callbacks & init excel connection if source is excel
 * @param {DataProviderCallbacks} dataCallbacks - data lifecycle callbacks
 */
ChartDataProvider.prototype.initWithCallbacks = function (dataCallbacks) {
    this.onChartConnectionOpened = dataCallbacks.onChartConnectionOpened || function () {};
    this.onInitChart = dataCallbacks.onInitChart || function () {};
    this._init();
}
/**
 * @private
 */
ChartDataProvider.prototype._initExcelConnection = function () {
    var self = this;
    var urls = this._getExcelUrls();
    this.excelConnection = new ChartConnection({
        "urls":urls,"autoReconnect":false});
    this.excelConnection.registerLifeCycleListeners({
        connectionOnOpenCallBack: function (e) {
            self.sendConfig();
        },
        connectionOnCloseCallBack: function (e) {    
        
        },
        connectionOnMsgCallBack: function (json) {
            self.onMessage(json,self.excelConnection);
            // self.msgSubject.next(json);
        }
    });
    this.excelConnection.connect();
}

/**
 * @private
 */
ChartDataProvider.prototype._getExcelUrls = function () {
    var websocketPath = getParameterByName("path") || "range";
    var websocketHost = getParameterByName("host") || "127.0.0.1";
    var websocketPort = getParameterByName("port") || "10080";
    var protocol = getParameterByName("protocol") || "ws";
    var url = protocol + "://" + websocketHost + ":" + websocketPort + "/" + encodeURIComponent(websocketPath);
    url = url + window.location.search;
    if (url.indexOf('?') < 0) url = url + "?1=1";
    return [url];
}

/**
 * @description input msg ,out side send msg to provider, provider will send out rx subject
 * @param {object} json - json object
 * @param {ChartConnection} [connection] -source connection
 * 
 */
ChartDataProvider.prototype.onMessage = function (json,connection) {
    this.logger.log("onMessage",json);

    if(isValued(json.command)){
        this._handleCommands(json);
    }else{
        this.subject.next(json);
    }
}

/**
 * @private
 */
ChartDataProvider.prototype._handleCommands = function (command) {
    var self = this;
    self.logger.log("Received command: ", command.command, command);

    switch (command.command) {
        case "config":
            this._processConfig(command);
            break;
        default:
            self.logger.log("Received unhandled command", command);
            break;
    }
}

/**
 * @private
 */
ChartDataProvider.prototype._processConfig = function (configCommand) {
    this.chartConfig = configCommand.payload;
    this.onInitChart(configCommand.payload);
    // TODO connect to chartconnection
    if(this.chartConnection == null || !this.chartConnection.connectionStatus){
        this._initChartConnection(configCommand);
    }
}

/**
 * @private
 */
ChartDataProvider.prototype._initChartConnection = function (configCommand){
    var self = this;
    var chartConfig = configCommand.chartConfig;
    var token = getParameterByName("token");
    if(isValued(token)){
        token = encodeURIComponent(token);
    }else{
        token = encodeURIComponent(configCommand.token);
    }
    var appId = getParameterByName("appId");
    if(isValued(appId)){
        appId = appId;
    }else{
        appId = configCommand.appId || newUUID();
    }
    this.appId = appId;

    var urls = getParameterByName("urls");
    if(isValued(urls)){
        urls = urls.split(",");
        urls = urls.map(url=> url.trim());
    }else{
        urls = configCommand.urls;
    }
    this.chartConnection = new ChartConnection({"urls":urls,"appId":appId,"token":token});
    this.chartConnection.registerLifeCycleListeners({
        connectionOnOpenCallBack: (e)=>{
            this.logger.info("chartConnection onOpen with urls",this.chartConnection.urls);
            this.onChartConnectionOpened(chartConfig,this.chartConnection);
        },
        connectionOnCloseCallBack: function (e) {
      
        },
        connectionOnMsgCallBack: function (json) {
            self.onMessage(json,self.chartConnection);

        }
    });
    this.chartConnection.connect();
}




/**
 * @private
 * @description create defaultDataCallback
 * @param {DataCallbacks} callbacks - input callbacks
 * @returns {DataCallbacks} defaultDataCallback
 */
ChartDataProvider.prototype._defaultDataCallback = function(callbacks){
    var func = ()=>{};
    callbacks = callbacks || {};
    callbacks.onStart  =  callbacks.onStart || func;
    callbacks.onEnd  =  callbacks.onEnd || func;
    callbacks.onError = callbacks.onError || function(error){self.logger.error("unhandler error",error)};
    callbacks.onResponse = callbacks.onResponse || function(data){self.logger.warn("unhandler history data",data)};
    callbacks.onEvent = callbacks.onEvent || function(data){self.logger.warn("unhandler realtime data",data)};
    return callbacks;
}


/**
  * @description this is a commander for init chartconfig. whis will be send  from client to server. when first time connected
  * @param {ConfigRequestOptions} requestOptions - request options for get more data
  * @param {DataCallbacks} callbacks - data response callbacks  for request
  * @param {ChartConnection} [connection] -data connection
  * @returns {subscription} subscription -rx subscription ,you can call unsubscribe when you want to unsubscribe.
  * 
  */
ChartDataProvider.prototype.configAndGetData = function (requestOptions,callbacks,connection){
    var self = this;
    var requestContext = new RequestContext(RequestContext.command.CONFIG,requestOptions);
    var subscription  = self._processResponseData(requestContext,callbacks);
    self.sendMsg(requestContext,connection);
    return subscription;
}

 /**
  * @description this is a new commander , for this commander ,server side no need clear requestBefore, just send zoomRequestData to client.
  * @param {MoreDataRequestOptions} requestOptions - request options for get more data
  * @param {DataCallbacks} callbacks - data response callbacks  for request
  * @returns {subscription} subscription -rx subscription ,you can call unsubscribe when you want to unsubscribe.
  * 
  */
ChartDataProvider.prototype.getMoreData = function (requestOptions,callbacks){
    var self = this;
    var requestContext = new RequestContext(RequestContext.command.INTERVAL_CHANGE,{
            chartName:"xxx",
            requestId:requestOptions.requestId,
            dataOptions: {
                startTime: requestOptions.startTime,
                endTime: requestOptions.endTime
        }
    });
    return self._processResponseData(requestContext,callbacks,true);
}





 /**
  * @description  this is a new commander , for this commander ,server side need clear requestBefore, and resend whole data to client.
  * 
  * @param {DataTypeChangeOptions} dataTypeChangeOptions - request options
  * @param {DataCallbacks} callbacks -data response callbacks  for request
  * @returns {subscription} subscription -rx subscription ,you can call unsubscribe when you want to unsubscribe.
  * 
  */
ChartDataProvider.prototype.getNewData = function(dataTypeChangeOptions,callbacks){
    var self = this;
    var requestContext = new RequestContext(RequestContext.command.INTERVAL_CHANGE,{
            chartName:"xxx",
            requestId:dataTypeChangeOptions.requestId,
            dataOptions: {
                sampleInterval: dataTypeChangeOptions.sampleInterval, 
                maxDataPoint: dataTypeChangeOptions.maxDataPoint
        }
    });
    return self._processResponseData(requestContext,callbacks);
}

/**
 * @private
 * @description bind callbacks to obervable and send msg to server
 * @param {RequestContext} requestContext
 * @param {DataCallbacks} callbacks -data response callbacks  for request
 * @param {boolean} isQueryOnly - default false
 * @returns {subscription} subscription -rx subscription ,you can call unsubscribe when you want to unsubscribe.
 */
ChartDataProvider.prototype._processResponseData = function (requestContext,callbacks,isQueryOnly){
    var self = this;
    isQueryOnly = isQueryOnly || false;
    if(!isValued(callbacks)){
        self.logger.error("callbacks is required!");
    }
    callbacks = self._defaultDataCallback(callbacks);
    var isEnd = false;
    var subscription =  this.subject.filter(m=>{
        return m.requestId == requestContext.requestId;
    }).subscribe(m=>{
        switch(m.msgType){
            case ChartResponse.msgType.Error:
                callbacks.onError(m.payload,m.requestId);
                break;
            case ChartResponse.msgType.startMark:
                isEnd = false;
                callbacks.onStart(m.payload,m.requestId);
                break; 
            case ChartResponse.msgType.endMark:
                isEnd = true;
                callbacks.onEnd(m.payload,m.requestId);
                if(isQueryOnly){
                    subscription.unsubscribe();
                }
                break;
            case ChartResponse.msgType.CHART_DATA:
                if(isEnd){
                    callbacks.onEvent(m.payload,m.requestId);
                }else{
                    callbacks.onResponse(m.payload,m.requestId);
                }
                break; 
            default:
                self.logger.warn("unhandle msgType",m);
                break;
        }
        
    });
    self.sendMsg(requestContext);
    return subscription;
}



ChartDataProvider.wrap = function (obj, method, func) {
    var proceed = obj[method];
    obj[method] = function () {
        var args = Array.prototype.slice.call(arguments);
        args.unshift(proceed);
        return func.apply(this, args);
    };
}


/**
 * ChartDataPayload
 * @typedef ChartDataPayload
 * @type {object}
 * @property {string} seriesName -seriesName for datapiont
 * @property {string} dataType -Ticked or Sampled,default Ticked
 * @property {object[]} data - datapoints
 */


/**
 * ChartResponse
 * @class
 * @param {string} requestId
 * @param {string} msgType
 * @param {object} payload
 */
function ChartResponse (requestId,msgType,payload){
    this.requestId = requestId;

    this.msgType = msgType;

    this.payload = payload;
}

ChartResponse.msgType = {
    CHART_DATA:"CHART_DATA",
    Error:"Error",
    startMark:"startMark",
    endMark:"endMark"
}


